// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -N -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "addrspace.h"
#include "noff.h"
#include "table.h"
#include "synch.h"


extern "C" { int bzero(char *, int); };

Table::Table(int s) : map(s), table(0), lock(0), size(s) {
    table = new void *[size];
    lock = new Lock("TableLock");
}

Table::~Table() {
    if (table) {
		delete table;
		table = 0;
    }
    if (lock) {
		delete lock;
		lock = 0;
    }
}

void *Table::Get(int i) {
    // Return the element associated with the given if, or 0 if
    // there is none.
	
    return (i >=0 && i < size && map.Test(i)) ? table[i] : 0;
}

int Table::Put(void *f) {
    // Put the element in the table and return the slot it used.  Use a
    // lock so 2 files don't get the same space.
    int i;	// to find the next slot
	
    lock->Acquire();
    i = map.Find();
    lock->Release();
    if ( i != -1)
		table[i] = f;
    return i;
}

void *Table::Remove(int i) {
    // Remove the element associated with identifier i from the table,
    // and return it.
	
    void *f =0;
	
    if ( i >= 0 && i < size ) {
		lock->Acquire();
		if ( map.Test(i) ) {
			map.Clear(i);
			f = table[i];
			table[i] = 0;
		}
		lock->Release();
    }
    return f;
}

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

static void 
SwapHeader (NoffHeader *noffH)
{
	noffH->noffMagic = WordToHost(noffH->noffMagic);
	noffH->code.size = WordToHost(noffH->code.size);
	noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
	noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
	noffH->initData.size = WordToHost(noffH->initData.size);
	noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
	noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
	noffH->uninitData.size = WordToHost(noffH->uninitData.size);
	noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
	noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::AddrSpace
// 	Create an address space to run a user program.
//	Load the program from a file "executable", and set everything
//	up so that we can start executing user instructions.
//
//	Assumes that the object code file is in NOFF format.
//
//	"executable" is the file containing the object code to load into memory
//
//      It's possible to fail to fully construct the address space for
//      several reasons, including being unable to allocate memory,
//      and being unable to read key parts of the executable.
//      Incompletely consretucted address spaces have the member
//      constructed set to false.
//----------------------------------------------------------------------

AddrSpace::AddrSpace(OpenFile *executableIn) : fileTable(MaxOpenFiles) 
{
	/*
	 *	The Noff header describes the contents of the rest of the file, giving
	 *	information about the program's instructions, initialized variables
	 *	and uninitialized variables. The Noff header resides at the very start
	 *	of the file and contains pointers to the remaining sections.
	 */
	NoffHeader noffH;
	unsigned int i, j, size;
	pageTableLock = new Lock("Translation Table Lock");
	// Don't allocate the input or output to disk files
	fileTable.Put(0);
	fileTable.Put(0);
	// reads the file header of the executable
	executableIn->ReadAt((char *)&noffH, sizeof(noffH), 0);
	// checks whether the executable is little endian or big endian
	if((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))
		SwapHeader(&noffH);
	/* 
	 *	noffMagic: A reserved "magic" number that indicates that the file is in
	 *	Noff format. The magic number is stored in the first four bytes of the
	 *	file. Before we attempt to execute a user-program, Nachos checks the
	 *	magic number to be sure that the file about to be executed is actually 
	 *	a Nachos executable.
	 */
	ASSERT(noffH.noffMagic == NOFFMAGIC);
	
	//size = noffH.code.size + noffH.initData.size + noffH.uninitData.size ;
	// numPages = divRoundUp(size, PageSize) + divRoundUp(UserStackSize,PageSize);
	//size = noffH.code.size + noffH.initData.size;
	//numCodeAndDataPages = divRoundUp(size, PageSize);
  	// we need to increase the size to leave room for the stack
	numCodePages = divRoundUp(noffH.code.size + noffH.initData.size, PageSize);
	numDataAndStackPages = divRoundUp((noffH.uninitData.size + UserStackSize), PageSize);
	numPages = numCodePages+ numDataAndStackPages;
	printf("Size: %d", divRoundUp((noffH.uninitData.size + noffH.code.size + noffH.initData.size), PageSize));
	printf("# of code and init data pages: %d\n", numCodePages);
	printf("# of uninit data & stack pages: %d\n", numDataAndStackPages);
	//printf(" ---> %i <---- \n", (noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize));

	DEBUG('s', "Number of pages: %d\n",numPages);
	DEBUG('s',"Number of stack pages: %d\n", divRoundUp(UserStackSize, PageSize));
	
	//printf("Initializing address space, num pages %d, size %d\n",numCodeAndDataPages,size);
	
	// first, set up the translation 
	pageTable = new VMTranslationEntry[numPages];
	pageTableLock->Acquire();
	//printf("Initializing Code & initData in Page Table\n");
	for (i = 0; i < numCodePages; i++) {
		pageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
		pageTable[i].valid = FALSE;
		pageTable[i].use = FALSE;
		pageTable[i].dirty = FALSE;
		pageTable[i].readOnly = FALSE;
		pageTable[i].pageType = CODE;
		pageTable[i].location = EXECUTABLE;
		pageTable[i].offset = noffH.code.inFileAddr + (i * PageSize);
	}
	//printf("Initializing Stack & uninitData in Page Table\n");
	for(i = (numCodePages); i < numPages; i++) {
		pageTable[i].virtualPage = i;
		pageTable[i].valid = FALSE;
		pageTable[i].use = FALSE;
		pageTable[i].dirty = FALSE;
		pageTable[i].readOnly = FALSE;
		pageTable[i].pageType = DATA;
		pageTable[i].location = NONE;
		pageTable[i].offset = 0;
	}
	//interrupt->Halt();
	pageTableLock->Release();
	executable = executableIn;
	
	// virtualAddr: What virtual address that segment begins at(normally zero)
	// inFileAddr: Pointer within the Noff file where that section actually
	// begins (so that Nachos can read it into memory before execution begins)
	// size: The size (in bytes) of that segment.
	// Using the page table, we should copy each page from each executable to 
	// a physical page in memory using its own page table

	sizeOfPageTable = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize;
	// numPages = divRoundUp(size, PageSize) + divRoundUp(UserStackSize,PageSize);
	DEBUG('a',"Initialized address space, num pages %d, size %d\n",numPages,size);
	
	// add new process to the process table here
	processTableLock->Acquire();
	/* set the new processtag's data here */
	ProcessTag* newProcess = new ProcessTag();
	newProcess->threads[0] = currentThread;
	newProcess->isDeleted = false;
	
	newProcess->currentThreadCount = 1;
	newProcess->activeThreadCount = 1;
	newProcess->nextThreadLocation++;
	/* find a place to insert this process into the process table */

	
	for(i=0; i < MAX_PROCESSES; i++)
	{
		if(processTable[i] == NULL || processTable[i]->isDeleted)
		{
			// insert the process into this location
			processTable[i] = newProcess;
			processId = i;
			/* increment the number of processes */
			currentProcessesRunning++;
			for(j=0; j<numPages; j++) {
				pageTable[j].processID = processId;
			}
			processTableLock->Release();
			//printf("Put addrspace into process table element %d. This addrspace range in physical memory: 0x%x to 0x%x\n", i,pageTable[0].physicalPage * PageSize, pageTable[numPages-1].physicalPage*PageSize); 
			return;
		}
	}

}

void AddrSpace::GetPageFromExecutable(VMTranslationEntry* entry) {
	// TODO: check that vpn is not too large, we need to be sure it's even in the executable
	executable->ReadAt(&(machine->mainMemory[(entry->physicalPage*PageSize)]), PageSize, entry->offset);
	
}

void AddrSpace::UpdateTranslation(VMTranslationEntry* update) {
	pageTableLock->Acquire();
	int index = update->virtualPage;
	pageTable[index].physicalPage = update->physicalPage;	// for now, virtual page # = phys page #
	pageTable[index].valid = update->valid;
	pageTable[index].use = update->use;
	pageTable[index].dirty = update->dirty;
	pageTable[index].readOnly = update->readOnly;
	pageTable[index].location = update->location;
	pageTable[index].offset = update->offset;
	pageTableLock->Release();
}

void AddrSpace::ZeroMemoryPage(int page) {
	//memoryBitMapLock->Acquire();
	bzero(&machine->mainMemory[page*PageSize], PageSize);
	//memoryBitMapLock->Release();
}

// AddrSpace::GiveNewStackSpace()
// Sets the stack register to the correct stack space based on the int passed in
unsigned int AddrSpace::GiveNewStackSpace(Thread* thread)
{
	DEBUG('z', "GIVE NEW STACK SPACE\n");
	processTableLock->Acquire();
	ProcessTag* currentProcess = processTable[currentThread->space->GetProcessId()];
	// look through the threads array and find an empty place
	int threadIndexOpen = -1;
	for(int i=0; i < MaxUserThreads; i++)
	{
		if(currentProcess->threads[i] == NULL || currentProcess->threads[i]->isDeleted)
		{
			// put the thread in this location then exit the loop
			// printf("Putting thread in location %d of this process's thread array.\n", i);
			currentProcess->threads[i] = thread;
			threadIndexOpen = i;
			// calculate the stack 
			int startingLocForNewStack = numPages - (PagesPerStack * threadIndexOpen);
			// printf("Starting loc for stack: %d\n", startingLocForNewStack);
			currentProcess->currentThreadCount++;
			currentProcess->activeThreadCount++;
			unsigned addrToNewStack = (startingLocForNewStack * PageSize) -16;
			// processTableLock->Release();
			return addrToNewStack;
		}
	}	
	printf("Something really bad happened in addrspace\n");
	return 0;
}

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
//
// 	Dealloate an address space.  release pages, page tables, files
// 	and file tables
//----------------------------------------------------------------------

AddrSpace::~AddrSpace()
{
    delete pageTable;
}

//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------

void
AddrSpace::InitRegisters()
{
    int i;
	processTableLock->Acquire();
	processTable[processId]->addrSpace = this;
	processTableLock->Release();
    for (i = 0; i < NumTotalRegs; i++)
		machine->WriteRegister(i, 0);
	
    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);	
	
    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);
	
	// Set the stack register to the end of the address space, where we
	// allocated the stack; but subtract off a bit, to make sure we don't
	// accidentally reference off the end!
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    DEBUG('a', "Initializing stack register to 0x%x\n", numPages * PageSize - 16);
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//
//	For now, nothing!
//----------------------------------------------------------------------

void AddrSpace::SaveState() 
{}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      For now, tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() 
{
	int i;
   // machine->pageTable = pageTable;
    //machine->pageTableSize = numPages;
	//iptLock->Acquire();
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	for(i=0; i<TLBSize; i++) {
		// PROPAGATE THE DIRTY BIT TO THE IPT HERE
		// JUST COPY THE DIRTY BIT IF THE VALID BIT IS TRUE
		
			if(machine->tlb[i].valid == TRUE)
				mmu->iptSetDirty(machine->tlb[i].physicalPage, machine->tlb[i].dirty);
			machine->tlb[i].valid = FALSE;
		
	}
	(void) interrupt->SetLevel(oldLevel);
	//iptLock->Release();
}

int AddrSpace::GetProcessId()
{
	return processId;
}

// Releases specified stack memory
void AddrSpace::ReleaseStackMem(int threadToDestroy)
{
	memoryBitMapLock->Acquire();
	DEBUG('s', "Thread #%d. Releasing 8 pages of memory from stack.\n", threadToDestroy);
	int bottomPageOfStack = numPages - (threadToDestroy * PagesPerStack);
	DEBUG('s', "Bottom of stack: %d\n", bottomPageOfStack);
	int topPageOfStack = bottomPageOfStack - PagesPerStack;
	DEBUG('s', "Top of stack: %d\n", topPageOfStack);
	pageTableLock->Acquire();
	for(int i=bottomPageOfStack; i > topPageOfStack; i--)
	{
		if(pageTable[i].valid == TRUE)
		{
			globalPhysicalMemoryBitMap->Clear(pageTable[i].physicalPage);
			// clear one page of stack from ipt
			iptLock->Acquire();
			int index, stackVPN;
			stackVPN = currentThread->stackSpaceAddr/PageSize;
			for(int j=bottomPageOfStack; j > topPageOfStack; j--) {
				index = mmu->iptFind(j, currentThread->space->GetProcessId());
				mmu->iptClearAt(index, currentThread->space->GetProcessId());
			}
			iptLock->Release();
		}
	}
	pageTableLock->Release();
	memoryBitMapLock->Release();
}

// Releases all memory in the page table for this address space
void AddrSpace::ReleaseAllMem()
{
	memoryBitMapLock->Acquire();
	// free up all pages allocated
	DEBUG('s', "Releasing all pages of memory allocated to this addrspace.\n");
	pageTableLock->Acquire();
	for(unsigned int i=0; i < numPages; i++)
	{
		if(pageTable[i].valid == TRUE) {
			// get the physical page number and call bitmap->Clear on that page number
			int physPageToClear = pageTable[i].physicalPage;
			globalPhysicalMemoryBitMap->Clear(physPageToClear);
		}
	}
	iptLock->Acquire();
	pageTableLock->Release();
	mmu->iptClearProcess(currentThread->space->GetProcessId());
	iptLock->Release();
	memoryBitMapLock->Release();
}

VMTranslationEntry* AddrSpace::FindTranslationEntry(int vpn) {
	
	return &pageTable[vpn];
}
